#pragma once
#include <queue>
#include <iostream>
#include <pthread.h>
#include <unistd.h>

const int defaultcap = 5;

template<class T>
class BlockQueue
{
public:
    BlockQueue(int cap = defaultcap)
        :_cap(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_full_cond, nullptr);
        pthread_cond_init(&_empty_cond, nullptr);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_full_cond);
        pthread_cond_destroy(&_empty_cond);
    }

    //生产者调用
    void Push(const T& data)
    {
        pthread_mutex_lock(&_mutex); // 加锁
        while(_q.size() == _cap)//如果队列满了，则让生产者阻塞等待
        {
            _psleep_num++;

            //问题1：如果等待失败，就会导致满了还生产
            //问题2：如果对方唤醒的不是一个生产者，而是所有
            //生产者，那么就有可能出现消费一个，生产好几个的情况，造成溢出
            //因此，要将if改为while，循环判断，确保满足条件再消费/生产。
            pthread_cond_wait(&_full_cond, &_mutex);
            _psleep_num--;
        }
        _q.push(data);

        //生产成功了，叫消费者去消费

        //问题：这个位置是否可以唤醒消费者？此时不能，因为当前生产者还持有锁，
        //消费者会在锁上等待。当前生产者解锁之后，消费者才会被唤醒。
        //但最终都会被唤醒，所以可以在这个位置进行唤醒。
        if(_csleep_num)
        {
            std::cout << "唤醒消费者" << std::endl;
            pthread_cond_signal(&_empty_cond);
        } 

        pthread_mutex_unlock(&_mutex); // 解锁
    }

    //消费者调用
    T Pop()
    {
        pthread_mutex_lock(&_mutex); // 加锁
        while(_q.empty())//如果队列为空，则让消费者阻塞等待
        {
            _csleep_num++;
            pthread_cond_wait(&_empty_cond, &_mutex);
            _csleep_num--;
        }
        T data = _q.front();
        _q.pop();

        //消费结束了，叫生产者去生产
        if(_psleep_num) 
        {
            std::cout << "唤醒生产者" << std::endl;
            pthread_cond_signal(&_full_cond);
        }
        
        pthread_mutex_unlock(&_mutex); // 解锁
        return data;
    }
private:
    std::queue<T> _q; // 临界资源
    int _cap;

    pthread_mutex_t _mutex;
    pthread_cond_t _full_cond; // 队列满时，生产者去等待
    pthread_cond_t _empty_cond; // 队列空时，消费者去等待

    int _csleep_num; // 正在休眠的消费者个数
    int _psleep_num; // 正在休眠的生产者个数
};